package exp12;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

public class AIPlayer {

    int level = 1;//默认随机下棋
    static int BOARD_SIZE = 15;//默认15，外部可传递值使之变化
    private static final Map<String, Integer> SCORE = new LinkedHashMap<>();//哈希表，便于查找
    int[][] boardStatus;
    private Point bestPoint;
    private int attack;
    private int ai;

    private enum ChessModel {//棋盘模型
        LIAN_WU(10000000, new String[]{"11111"}), // 连五
        HUO_SI(1000000, new String[]{"011110"}),// 活四
        HUO_SAN(10000, new String[]{"001110", "011100", "010110", "011010"}),// 活三
        CHONG_SI(9000, new String[]{"11110", "01111", "10111", "11011", "11101"}),//冲四
        HUO_ER(100, new String[]{"001100", "011000", "000110", "001010", "010100"}),// 活二
        HUO_YI(80, new String[]{"010200", "002010", "020100", "001020", "201000", "000102", "000201"}),// 活一
        MIAN_SAN(30, new String[]{"001112", "010112", "011012", "211100", "211010"}), // 眠三
        MIAN_ER(10, new String[]{"011200", "001120", "002110", "021100", "110000", "000011", "000112", "211000"}),// 眠二
        MIAN_YI(1, new String[]{"001200", "002100", "000210", "000120", "210000", "000012"});  // 眠一
        final int score;//分数
        final String[] values;//局势数组

        ChessModel(int score, String[] values) {
            this.score = score;
            this.values = values;
        }

        @Override
        public String toString() {
            return Arrays.toString(this.values);
        }
    }

    static {
        // 初始化棋型分数表，便于访存数据
        for (ChessModel chessScore : ChessModel.values()) {
            for (String value : chessScore.values) {
                SCORE.put(value, chessScore.score);
            }
        }
    }

    public AIPlayer(int level, int BOARD_SIZE) {
        this.level = level;
        AIPlayer.BOARD_SIZE = BOARD_SIZE;
    }


    public Point getPosition(int[][] boardStatus, Point point) {
//        System.out.println("Last round human position is" + humanPosition[0] + "," + humanPosition[1]);
        //获取位置信息
        this.boardStatus = boardStatus;
        return switch (this.level) {
            case 1 -> randomPosition();
            case 2 -> lowerLevelPosition(point);
            case 3 -> highLevelPosition(point);
            default -> randomPosition();
        };
    }


    private Point lowerLevelPosition(Point point) {
        int score = -10000000;
        Point bestPos = new Point(0, 0, 0);
        if (point.x == -1 && point.y == -1) {//如果是第一步下棋
            bestPos.x = BOARD_SIZE / 2;
            bestPos.y = BOARD_SIZE / 2;
            return bestPos;
        }
        /* 遍历所有能下棋的点位，评估各个点位的分值，选择分值最大的点位 */
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (this.boardStatus[i][j] != 0) {
                    // 该点已有棋子，跳过
                    continue;
                }
                // 评估该点AI得分
                int val = evaluate(new Point(i, j, 0));
                // 选择得分最高的点位
                if (val > score) {
                    // 最高分被刷新
                    score = val;
                    // 更新最佳点位
                    bestPos.x = i;
                    bestPos.y = j;
                }
            }
        }
        return bestPos;
    }

    //EXP11 Start
    private int evaluate(Point point) {
        //Exp11 此处进行改进优化
        int score = 0;
        int huoSanNum = 0;
        int chongSiNum = 0;
        int huoErNum = 0;

        for (int i = 1; i < 5; i++) {
            String situation = getSituation(point, i);
            if (checkSituation(situation, ChessModel.HUO_SAN)) {
                huoSanNum++;
            } else if (checkSituation(situation, ChessModel.CHONG_SI)) {
                chongSiNum++;
            } else if (checkSituation(situation, ChessModel.HUO_ER)) {
                huoErNum++;
            }
            score += getScore(situation);
        }
        if (huoSanNum > 0 && huoErNum > 0) {
            score *= 2;
        }
        if (chongSiNum > 0 && huoErNum > 0) {
            score *= 4;
        }
        if (huoSanNum > 1) {
            score *= 6;
        }
        if (chongSiNum > 0 && huoSanNum > 0) {
            score += 8;
        }
        return score;
    }

    private int getScore(String situation) {
        //依据当前的棋盘返回分数
        for (String key : SCORE.keySet()) {
            if (situation.contains(key)) {
                return SCORE.get(key);
            }
        }
        return 0;
    }

    private String getSituation(Point point, int direction) {
        //根据落点位置来确认棋盘局势，大方向 1.横 2.纵 3.左斜 4.右斜，实际上就是将中心点视作AI下棋位置，然后将四个大方向的棋子分别加入到stringBuffer中
        direction = direction * 2 - 1;
        StringBuffer stringBuffer = new StringBuffer();
        appendChess(stringBuffer, point, direction, 4);
        appendChess(stringBuffer, point, direction, 3);
        appendChess(stringBuffer, point, direction, 2);
        appendChess(stringBuffer, point, direction, 1);
        stringBuffer.append(1);//当前棋子标为AI
        appendChess(stringBuffer, point, direction + 1, 1);
        appendChess(stringBuffer, point, direction + 1, 2);
        appendChess(stringBuffer, point, direction + 1, 3);
        appendChess(stringBuffer, point, direction + 1, 4);
        return stringBuffer.toString();
    }

    private boolean checkSituation(String situation, ChessModel chessModel) {
        //判定是否有其他类型的棋子
        for (String value : chessModel.values) {
            if (situation.contains(value)) return true;
        }
        return false;
    }

    private void appendChess(StringBuffer sb, Point point, int direction, int offset) {
        //依据方向加棋子
        int chess = relativePoint(point, direction, offset);
        if (chess > -1) {
            if (this.boardStatus[point.x][point.y] == 2) { //便于后续拓展，当评估人类棋手时。
                if (chess > 0) {
                    chess = 3 - chess;
                }
            }
            sb.append(chess);
        }
    }

    private int relativePoint(Point point, int direction, int offset) {
        //point代表当前棋子位置，direction是八个不同方向,offset表示偏移量
        int x = point.x, y = point.y;
        switch (direction) {
            case 1:
                x -= offset;
                break;
            case 2:
                x += offset;
                break;
            case 3:
                y -= offset;
                break;
            case 4:
                y += offset;
                break;
            case 5:
                x += offset;
                y -= offset;
                break;
            case 6:
                x -= offset;
                y += offset;
                break;
            case 7:
                x -= offset;
                y -= offset;
                break;
            case 8:
                x += offset;
                y += offset;
                break;
        }

        if (x < 0 || y < 0 || x >= BOARD_SIZE || y >= BOARD_SIZE) {
            // 越界
            return -1;
        }

        // 返回该位置的棋子值
        return this.boardStatus[x][y];
    }
    //EXP11 End


    //EXP12 Start
    private Point highLevelPosition(Point point) {
        // exp12 此处进行改进优化，注意调用Exp11中的相关函数。
        this.ai = 3 - point.type;
        this.bestPoint = null;
        this.attack = 10;

//        int score = -10000000;
        if (point.x == -1 && point.y == -1) {//如果是第一步下棋
            Point bestPos = new Point(0, 0, 0);
            bestPos.x = BOARD_SIZE / 2;
            bestPos.y = BOARD_SIZE / 2;
            return bestPos;
        }


        // 基于极大极小值搜索获取最佳棋位
        minimax(0, 2);

        return this.bestPoint;

    }


    private int minimax(int type, int depth) {
        // 是否是根节点
        boolean isRoot = type == 0;
        if (isRoot) {
            // 根节点是AI走棋
            type = this.ai;
        }

        // 当前是否是AI走棋
        boolean isAI = type == this.ai;
        // 当前分值，
        int score;
        if (isAI) {
            // AI因为要选择最高分，所以初始化一个难以到达的低分
            score = -999999999;
        } else {
            // 对手要选择最低分，所以初始化一个难以到达的高分
            score = 999999999;
        }

        // 到达叶子结点
        if (depth == 0) {
            /**
             * 评估每棵博弈树的叶子结点的局势
             * 比如：depth=2时，表示从AI开始走两步棋之后的局势评估，AI(走第一步) -> 玩家(走第二步)，然后对局势进行评估
             * 注意：局势评估是以AI角度进行的，分值越大对AI越有利，对玩家越不利
             */
            return evaluateAll();
        }

        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (this.boardStatus[i][j] != 0) {
                    // 该处已有棋子，跳过
                    continue;
                }

                /* 模拟 AI -> 玩家 交替落子 */
                Point p = new Point(i, j, type);
                // 落子
                putChess(p);
                // 递归生成博弈树，并评估叶子结点的局势获取分值
                int curScore = minimax(3 - type, depth - 1);
                // 撤销落子
                revokeChess(p);

                if (isAI) {
                    // AI要选对自己最有利的节点（分最高的）
                    if (curScore > score) {
                        // 最高值被刷新
                        score = curScore;
                        if (isRoot) {
                            // 根节点处更新AI最好的棋位
                            this.bestPoint = p;
                        }
                    }
                } else {
                    // 对手要选对AI最不利的节点（分最低的）
                    if (curScore < score) {
                        // 最低值被刷新
                        score = curScore;
                    }
                }
            }
        }

        return score;
    }

    /**
     * 下棋子
     *
     * @param point 棋子
     */
    private void putChess(Point point) {
        this.boardStatus[point.x][point.y] = point.type;
    }

    /**
     * 撤销下的棋子
     *
     * @param point 棋子
     */
    private void revokeChess(Point point) {
        this.boardStatus[point.x][point.y] = 0;
    }

    /**
     * 以AI角度对当前局势进行评估，分数越大对AI越有利
     *
     */
    private int evaluateAll() {
        // AI得分
        int aiScore = 0;
        // 对手得分
        int foeScore = 0;

        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                int type = this.boardStatus[i][j];
                if (type == 0) {
                    // 该点没有棋子，跳过
                    continue;
                }
                // 评估该棋位分值
                int val = evaluate(new Point(i, j, type));
                if (type == this.ai) {
                    // 累积AI得分
                    aiScore += val;
                } else {
                    // 累积对手得分
                    foeScore += val;
                }
            }
        }

        // 该局AI最终得分 = AI得分 * 进攻系数 - 对手得分
        return aiScore * this.attack - foeScore;
    }
    //EXP12 End

    private Point randomPosition() {//无脑下棋
        int x = (int) ((BOARD_SIZE - 1) * Math.random());
        int y = (int) ((BOARD_SIZE - 1) * Math.random());
        return new Point(x, y, 0);
    }

}



